package com.example.auth.aspect;

import com.example.auth.annotation.RedisCache;
import com.example.auth.util.RedisCacheUtils;
import com.example.common.constant.ServiceConstants;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * redis cache aspect
 *
 * @author cui haida
 */
@Slf4j
@Aspect
@Component
public class RedisCacheAspect {

    /**
     * redisTemplate
     */
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * define the pointcut, which is the pointcut of the annotation
     */
    @Pointcut("@annotation(com.example.auth.annotation.RedisCache)")
    public void pointcut() {}

    /**
     * define the around handler for the pointcut
     * @param point join point
     * @return the handler res
     * @throws Throwable if any error occurs, throw it
     */
    @Around("pointcut()")
    public Object handle(ProceedingJoinPoint point) throws Throwable {
        // get the method from join point
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // get the annotation from method
        RedisCache cache = method.getAnnotation(RedisCache.class);
        // get whole cache key by join point and prefix
        String cacheKey = RedisCacheUtils.getKey(point, ServiceConstants.CACHE_AUTH_PREFIX);
        // clear cache if flush is true
        if (cache.flush()) {
            // get all keys by class prefix
            String classPrefix = RedisCacheUtils.getKeyOfClassPrefix(point, ServiceConstants.CACHE_AUTH_PREFIX);
            Set<String> keys = redisTemplate.keys(classPrefix + "*");
            // do delete
            redisTemplate.delete(keys);
            log.debug("clear cache：{}", keys);
            return point.proceed();
        }

        // if it has key in redis, will get from cache, else will set in cache
        Boolean hasKey = redisTemplate.hasKey(cacheKey);
        Object result;
        if (hasKey) {
            result = redisTemplate.opsForValue().get(cacheKey);
            log.debug("get from cache：{}", cacheKey);
        } else {
            result = point.proceed();
            log.debug("get from db：{}", cacheKey);
            redisTemplate.opsForValue().set(cacheKey, result, cache.expire(), cache.unit());
            log.debug("set into cache：{}", cacheKey);
        }
        return result;
    }
}